{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Anna KaRNNa\n",
    "\n",
    "In this notebook, I'll build a character-wise RNN trained on Anna Karenina, one of my all-time favorite books. It'll be able to generate new text based on the text from the book.\n",
    "\n",
    "This network is based off of Andrej Karpathy's [post on RNNs](http://karpathy.github.io/2015/05/21/rnn-effectiveness/) and [implementation in Torch](https://github.com/karpathy/char-rnn). Also, some information [here at r2rt](http://r2rt.com/recurrent-neural-networks-in-tensorflow-ii.html) and from [Sherjil Ozair](https://github.com/sherjilozair/char-rnn-tensorflow) on GitHub. Below is the general architecture of the character-wise RNN.\n",
    "\n",
    "<img src=\"assets/charseq.jpeg\" width=\"500\">"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import time\n",
    "from collections import namedtuple\n",
    "\n",
    "import numpy as np\n",
    "import tensorflow as tf"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First we'll load the text file and convert it into integers for our network to use. Here I'm creating a couple dictionaries to convert the characters to and from integers. Encoding the characters as integers makes it easier to use as input in the network."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "with open('anna.txt', 'r') as f:\n",
    "    text=f.read()\n",
    "vocab = set(text)\n",
    "vocab_to_int = {c: i for i, c in enumerate(vocab)}\n",
    "int_to_vocab = dict(enumerate(vocab))\n",
    "encoded = np.array([vocab_to_int[c] for c in text], dtype=np.int32)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's check out the first 100 characters, make sure everything is peachy. According to the [American Book Review](http://americanbookreview.org/100bestlines.asp), this is the 6th best first line of a book ever."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Chapter 1\\n\\n\\nHappy families are all alike; every unhappy family is unhappy in its own\\nway.\\n\\nEverythin'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "text[:100]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we can see the characters encoded as integers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([55, 63, 69, 22,  6, 76, 45,  5, 16, 35, 35, 35,  2, 69, 22, 22, 33,\n",
       "        5, 58, 69,  1, 29, 43, 29, 76, 78,  5, 69, 45, 76,  5, 69, 43, 43,\n",
       "        5, 69, 43, 29, 17, 76, 34,  5, 76, 48, 76, 45, 33,  5, 39, 12, 63,\n",
       "       69, 22, 22, 33,  5, 58, 69,  1, 29, 43, 33,  5, 29, 78,  5, 39, 12,\n",
       "       63, 69, 22, 22, 33,  5, 29, 12,  5, 29,  6, 78,  5, 52, 36, 12, 35,\n",
       "       36, 69, 33, 61, 35, 35, 44, 48, 76, 45, 33,  6, 63, 29, 12], dtype=int32)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "encoded[:100]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since the network is working with individual characters, it's similar to a classification problem in which we are trying to predict the next character from the previous text.  Here's how many 'classes' our network has to pick from."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "83"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "len(vocab)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Making training mini-batches\n",
    "\n",
    "Here is where we'll make our mini-batches for training. Remember that we want our batches to be multiple sequences of some desired number of sequence steps. Considering a simple example, our batches would look like this:\n",
    "\n",
    "<img src=\"assets/sequence_batching@1x.png\" width=500px>\n",
    "\n",
    "\n",
    "<br>\n",
    "We have our text encoded as integers as one long array in `encoded`. Let's create a function that will give us an iterator for our batches. I like using [generator functions](https://jeffknupp.com/blog/2013/04/07/improve-your-python-yield-and-generators-explained/) to do this. Then we can pass `encoded` into this function and get our batch generator.\n",
    "\n",
    "The first thing we need to do is discard some of the text so we only have completely full batches. Each batch contains $N \\times M$ characters, where $N$ is the batch size (the number of sequences) and $M$ is the number of steps. Then, to get the number of batches we can make from some array `arr`, you divide the length of `arr` by the batch size. Once you know the number of batches and the batch size, you can get the total number of characters to keep.\n",
    "\n",
    "After that, we need to split `arr` into $N$ sequences. You can do this using `arr.reshape(size)` where `size` is a tuple containing the dimensions sizes of the reshaped array. We know we want $N$ sequences (`n_seqs` below), let's make that the size of the first dimension. For the second dimension, you can use `-1` as a placeholder in the size, it'll fill up the array with the appropriate data for you. After this, you should have an array that is $N \\times (M * K)$ where $K$ is the number of batches.\n",
    "\n",
    "Now that we have this array, we can iterate through it to get our batches. The idea is each batch is a $N \\times M$ window on the array. For each subsequent batch, the window moves over by `n_steps`. We also want to create both the input and target arrays. Remember that the targets are the inputs shifted over one character. You'll usually see the first input character used as the last target character, so something like this:\n",
    "```python\n",
    "y[:, :-1], y[:, -1] = x[:, 1:], x[:, 0]\n",
    "```\n",
    "where `x` is the input batch and `y` is the target batch.\n",
    "\n",
    "The way I like to do this window is use `range` to take steps of size `n_steps` from $0$ to `arr.shape[1]`, the total number of steps in each sequence. That way, the integers you get from `range` always point to the start of a batch, and each window is `n_steps` wide."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def get_batches(arr, n_seqs, n_steps):\n",
    "    '''Create a generator that returns batches of size\n",
    "       n_seqs x n_steps from arr.\n",
    "       \n",
    "       Arguments\n",
    "       ---------\n",
    "       arr: Array you want to make batches from\n",
    "       n_seqs: Batch size, the number of sequences per batch\n",
    "       n_steps: Number of sequence steps per batch\n",
    "    '''\n",
    "    # Get the number of characters per batch and number of batches we can make\n",
    "    characters_per_batch = n_seqs * n_steps\n",
    "    n_batches = len(arr)//characters_per_batch\n",
    "    \n",
    "    # Keep only enough characters to make full batches\n",
    "    arr = arr[:n_batches * characters_per_batch]\n",
    "    \n",
    "    # Reshape into n_seqs rows\n",
    "    arr = arr.reshape((n_seqs, -1))\n",
    "    \n",
    "    for n in range(0, arr.shape[1], n_steps):\n",
    "        # The features\n",
    "        x = arr[:, n:n+n_steps]\n",
    "        # The targets, shifted by one\n",
    "        y = np.zeros_like(x)\n",
    "        y[:, :-1], y[:, -1] = x[:, 1:], x[:, 0]\n",
    "        yield x, y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now I'll make my data sets and we can check out what's going on here. Here I'm going to use a batch size of 10 and 50 sequence steps."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "batches = get_batches(encoded, 10, 50)\n",
    "x, y = next(batches)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x\n",
      " [[55 63 69 22  6 76 45  5 16 35]\n",
      " [ 5 69  1  5 12 52  6  5 56 52]\n",
      " [48 29 12 61 35 35  8 64 76 78]\n",
      " [12  5 24 39 45 29 12 56  5 63]\n",
      " [ 5 29  6  5 29 78 28  5 78 29]\n",
      " [ 5 13  6  5 36 69 78 35 52 12]\n",
      " [63 76 12  5 18 52  1 76  5 58]\n",
      " [34  5 73 39  6  5 12 52 36  5]\n",
      " [ 6  5 29 78 12 79  6 61  5 59]\n",
      " [ 5 78 69 29 24  5  6 52  5 63]]\n",
      "\n",
      "y\n",
      " [[63 69 22  6 76 45  5 16 35 35]\n",
      " [69  1  5 12 52  6  5 56 52 29]\n",
      " [29 12 61 35 35  8 64 76 78 28]\n",
      " [ 5 24 39 45 29 12 56  5 63 29]\n",
      " [29  6  5 29 78 28  5 78 29 45]\n",
      " [13  6  5 36 69 78 35 52 12 43]\n",
      " [76 12  5 18 52  1 76  5 58 52]\n",
      " [ 5 73 39  6  5 12 52 36  5 78]\n",
      " [ 5 29 78 12 79  6 61  5 59 63]\n",
      " [78 69 29 24  5  6 52  5 63 76]]\n"
     ]
    }
   ],
   "source": [
    "print('x\\n', x[:10, :10])\n",
    "print('\\ny\\n', y[:10, :10])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you implemented `get_batches` correctly, the above output should look something like \n",
    "```\n",
    "x\n",
    " [[55 63 69 22  6 76 45  5 16 35]\n",
    " [ 5 69  1  5 12 52  6  5 56 52]\n",
    " [48 29 12 61 35 35  8 64 76 78]\n",
    " [12  5 24 39 45 29 12 56  5 63]\n",
    " [ 5 29  6  5 29 78 28  5 78 29]\n",
    " [ 5 13  6  5 36 69 78 35 52 12]\n",
    " [63 76 12  5 18 52  1 76  5 58]\n",
    " [34  5 73 39  6  5 12 52 36  5]\n",
    " [ 6  5 29 78 12 79  6 61  5 59]\n",
    " [ 5 78 69 29 24  5  6 52  5 63]]\n",
    "\n",
    "y\n",
    " [[63 69 22  6 76 45  5 16 35 35]\n",
    " [69  1  5 12 52  6  5 56 52 29]\n",
    " [29 12 61 35 35  8 64 76 78 28]\n",
    " [ 5 24 39 45 29 12 56  5 63 29]\n",
    " [29  6  5 29 78 28  5 78 29 45]\n",
    " [13  6  5 36 69 78 35 52 12 43]\n",
    " [76 12  5 18 52  1 76  5 58 52]\n",
    " [ 5 73 39  6  5 12 52 36  5 78]\n",
    " [ 5 29 78 12 79  6 61  5 59 63]\n",
    " [78 69 29 24  5  6 52  5 63 76]]\n",
    " ```\n",
    " although the exact numbers will be different. Check to make sure the data is shifted over one step for `y`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Building the model\n",
    "\n",
    "Below is where you'll build the network. We'll break it up into parts so it's easier to reason about each bit. Then we can connect them up into the whole network.\n",
    "\n",
    "<img src=\"assets/charRNN.png\" width=500px>\n",
    "\n",
    "\n",
    "### Inputs\n",
    "\n",
    "First off we'll create our input placeholders. As usual we need placeholders for the training data and the targets. We'll also create a placeholder for dropout layers called `keep_prob`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def build_inputs(batch_size, num_steps):\n",
    "    ''' Define placeholders for inputs, targets, and dropout \n",
    "    \n",
    "        Arguments\n",
    "        ---------\n",
    "        batch_size: Batch size, number of sequences per batch\n",
    "        num_steps: Number of sequence steps in a batch\n",
    "        \n",
    "    '''\n",
    "    # Declare placeholders we'll feed into the graph\n",
    "    inputs = tf.placeholder(tf.int32, [batch_size, num_steps], name='inputs')\n",
    "    targets = tf.placeholder(tf.int32, [batch_size, num_steps], name='targets')\n",
    "    \n",
    "    # Keep probability placeholder for drop out layers\n",
    "    keep_prob = tf.placeholder(tf.float32, name='keep_prob')\n",
    "    \n",
    "    return inputs, targets, keep_prob"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### LSTM Cell\n",
    "\n",
    "Here we will create the LSTM cell we'll use in the hidden layer. We'll use this cell as a building block for the RNN. So we aren't actually defining the RNN here, just the type of cell we'll use in the hidden layer.\n",
    "\n",
    "We first create a basic LSTM cell with\n",
    "\n",
    "```python\n",
    "lstm = tf.contrib.rnn.BasicLSTMCell(num_units)\n",
    "```\n",
    "\n",
    "where `num_units` is the number of units in the hidden layers in the cell. Then we can add dropout by wrapping it with \n",
    "\n",
    "```python\n",
    "tf.contrib.rnn.DropoutWrapper(lstm, output_keep_prob=keep_prob)\n",
    "```\n",
    "You pass in a cell and it will automatically add dropout to the inputs or outputs. Finally, we can stack up the LSTM cells into layers with [`tf.contrib.rnn.MultiRNNCell`](https://www.tensorflow.org/versions/r1.0/api_docs/python/tf/contrib/rnn/MultiRNNCell). With this, you pass in a list of cells and it will send the output of one cell into the next cell. For example,\n",
    "\n",
    "```python\n",
    "tf.contrib.rnn.MultiRNNCell([cell]*num_layers)\n",
    "```\n",
    "\n",
    "This might look a little weird if you know Python well because this will create a list of the same `cell` object. However, TensorFlow will create different weight matrices for all `cell` objects. Even though this is actually multiple LSTM cells stacked on each other, you can treat the multiple layers as one cell.\n",
    "\n",
    "We also need to create an initial cell state of all zeros. This can be done like so\n",
    "\n",
    "```python\n",
    "initial_state = cell.zero_state(batch_size, tf.float32)\n",
    "```\n",
    "\n",
    "Below, we implement the `build_lstm` function to create these LSTM cells and the initial state."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def build_lstm(lstm_size, num_layers, batch_size, keep_prob):\n",
    "    ''' Build LSTM cell.\n",
    "    \n",
    "        Arguments\n",
    "        ---------\n",
    "        keep_prob: Scalar tensor (tf.placeholder) for the dropout keep probability\n",
    "        lstm_size: Size of the hidden layers in the LSTM cells\n",
    "        num_layers: Number of LSTM layers\n",
    "        batch_size: Batch size\n",
    "\n",
    "    '''\n",
    "    ### Build the LSTM Cell\n",
    "    # Use a basic LSTM cell\n",
    "    lstm = tf.contrib.rnn.BasicLSTMCell(lstm_size)\n",
    "    \n",
    "    # Add dropout to the cell\n",
    "    drop = tf.contrib.rnn.DropoutWrapper(lstm, output_keep_prob=keep_prob)\n",
    "    \n",
    "    # Stack up multiple LSTM layers, for deep learning\n",
    "    cell = tf.contrib.rnn.MultiRNNCell([drop] * num_layers)\n",
    "    initial_state = cell.zero_state(batch_size, tf.float32)\n",
    "    \n",
    "    return cell, initial_state"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### RNN Output\n",
    "\n",
    "Here we'll create the output layer. We need to connect the output of the RNN cells to a full connected layer with a softmax output. The softmax output gives us a probability distribution we can use to predict the next character.\n",
    "\n",
    "If our input has batch size $N$, number of steps $M$, and the hidden layer has $L$ hidden units, then the output is a 3D tensor with size $N \\times M \\times L$. The output of each LSTM cell has size $L$, we have $M$ of them, one for each sequence step, and we have $N$ sequences. So the total size is $N \\times M \\times L$.\n",
    "\n",
    "We are using the same fully connected layer, the same weights, for each of the outputs. Then, to make things easier, we should reshape the outputs into a 2D tensor with shape $(M * N) \\times L$. That is, one row for each sequence and step, where the values of each row are the output from the LSTM cells.\n",
    "\n",
    "One we have the outputs reshaped, we can do the matrix multiplication with the weights. We need to wrap the weight and bias variables in a variable scope with `tf.variable_scope(scope_name)` because there are weights being created in the LSTM cells. TensorFlow will throw an error if the weights created here have the same names as the weights created in the LSTM cells, which they will be default. To avoid this, we wrap the variables in a variable scope so we can give them unique names."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def build_output(lstm_output, in_size, out_size):\n",
    "    ''' Build a softmax layer, return the softmax output and logits.\n",
    "    \n",
    "        Arguments\n",
    "        ---------\n",
    "        \n",
    "        x: Input tensor\n",
    "        in_size: Size of the input tensor, for example, size of the LSTM cells\n",
    "        out_size: Size of this softmax layer\n",
    "    \n",
    "    '''\n",
    "\n",
    "    # Reshape output so it's a bunch of rows, one row for each step for each sequence.\n",
    "    # That is, the shape should be batch_size*num_steps rows by lstm_size columns\n",
    "    seq_output = tf.concat(lstm_output, axis=1)\n",
    "    x = tf.reshape(seq_output, [-1, in_size])\n",
    "    \n",
    "    # Connect the RNN outputs to a softmax layer\n",
    "    with tf.variable_scope('softmax'):\n",
    "        softmax_w = tf.Variable(tf.truncated_normal((in_size, out_size), stddev=0.1))\n",
    "        softmax_b = tf.Variable(tf.zeros(out_size))\n",
    "    \n",
    "    # Since output is a bunch of rows of RNN cell outputs, logits will be a bunch\n",
    "    # of rows of logit outputs, one for each step and sequence\n",
    "    logits = tf.matmul(x, softmax_w) + softmax_b\n",
    "    \n",
    "    # Use softmax to get the probabilities for predicted characters\n",
    "    out = tf.nn.softmax(logits, name='predictions')\n",
    "    \n",
    "    return out, logits"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Training loss\n",
    "\n",
    "Next up is the training loss. We get the logits and targets and calculate the softmax cross-entropy loss. First we need to one-hot encode the targets, we're getting them as encoded characters. Then, reshape the one-hot targets so it's a 2D tensor with size $(M*N) \\times C$ where $C$ is the number of classes/characters we have. Remember that we reshaped the LSTM outputs and ran them through a fully connected layer with $C$ units. So our logits will also have size $(M*N) \\times C$.\n",
    "\n",
    "Then we run the logits and targets through `tf.nn.softmax_cross_entropy_with_logits` and find the mean to get the loss."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def build_loss(logits, targets, lstm_size, num_classes):\n",
    "    ''' Calculate the loss from the logits and the targets.\n",
    "    \n",
    "        Arguments\n",
    "        ---------\n",
    "        logits: Logits from final fully connected layer\n",
    "        targets: Targets for supervised learning\n",
    "        lstm_size: Number of LSTM hidden units\n",
    "        num_classes: Number of classes in targets\n",
    "        \n",
    "    '''\n",
    "    \n",
    "    # One-hot encode targets and reshape to match logits, one row per batch_size per step\n",
    "    y_one_hot = tf.one_hot(targets, num_classes)\n",
    "    y_reshaped = tf.reshape(y_one_hot, logits.get_shape())\n",
    "    \n",
    "    # Softmax cross entropy loss\n",
    "    loss = tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y_reshaped)\n",
    "    loss = tf.reduce_mean(loss)\n",
    "    return loss"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Optimizer\n",
    "\n",
    "Here we build the optimizer. Normal RNNs have have issues gradients exploding and disappearing. LSTMs fix the disappearance problem, but the gradients can still grow without bound. To fix this, we can clip the gradients above some threshold. That is, if a gradient is larger than that threshold, we set it to the threshold. This will ensure the gradients never grow overly large. Then we use an AdamOptimizer for the learning step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def build_optimizer(loss, learning_rate, grad_clip):\n",
    "    ''' Build optmizer for training, using gradient clipping.\n",
    "    \n",
    "        Arguments:\n",
    "        loss: Network loss\n",
    "        learning_rate: Learning rate for optimizer\n",
    "    \n",
    "    '''\n",
    "    \n",
    "    # Optimizer for training, using gradient clipping to control exploding gradients\n",
    "    tvars = tf.trainable_variables()\n",
    "    grads, _ = tf.clip_by_global_norm(tf.gradients(loss, tvars), grad_clip)\n",
    "    train_op = tf.train.AdamOptimizer(learning_rate)\n",
    "    optimizer = train_op.apply_gradients(zip(grads, tvars))\n",
    "    \n",
    "    return optimizer"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Build the network\n",
    "\n",
    "Now we can put all the pieces together and build a class for the network. To actually run data through the LSTM cells, we will use [`tf.nn.dynamic_rnn`](https://www.tensorflow.org/versions/r1.0/api_docs/python/tf/nn/dynamic_rnn). This function will pass the hidden and cell states across LSTM cells appropriately for us. It returns the outputs for each LSTM cell at each step for each sequence in the mini-batch. It also gives us the final LSTM state. We want to save this state as `final_state` so we can pass it to the first LSTM cell in the the next mini-batch run. For `tf.nn.dynamic_rnn`, we pass in the cell and initial state we get from `build_lstm`, as well as our input sequences. Also, we need to one-hot encode the inputs before going into the RNN. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "class CharRNN:\n",
    "    \n",
    "    def __init__(self, num_classes, batch_size=64, num_steps=50, \n",
    "                       lstm_size=128, num_layers=2, learning_rate=0.001, \n",
    "                       grad_clip=5, sampling=False):\n",
    "    \n",
    "        # When we're using this network for sampling later, we'll be passing in\n",
    "        # one character at a time, so providing an option for that\n",
    "        if sampling == True:\n",
    "            batch_size, num_steps = 1, 1\n",
    "        else:\n",
    "            batch_size, num_steps = batch_size, num_steps\n",
    "\n",
    "        tf.reset_default_graph()\n",
    "        \n",
    "        # Build the input placeholder tensors\n",
    "        self.inputs, self.targets, self.keep_prob = build_inputs(batch_size, num_steps)\n",
    "\n",
    "        # Build the LSTM cell\n",
    "        cell, self.initial_state = build_lstm(lstm_size, num_layers, batch_size, self.keep_prob)\n",
    "\n",
    "        ### Run the data through the RNN layers\n",
    "        # First, one-hot encode the input tokens\n",
    "        x_one_hot = tf.one_hot(self.inputs, num_classes)\n",
    "        \n",
    "        # Run each sequence step through the RNN and collect the outputs\n",
    "        outputs, state = tf.nn.dynamic_rnn(cell, x_one_hot, initial_state=self.initial_state)\n",
    "        self.final_state = state\n",
    "        \n",
    "        # Get softmax predictions and logits\n",
    "        self.prediction, self.logits = build_output(outputs, lstm_size, num_classes)\n",
    "        \n",
    "        # Loss and optimizer (with gradient clipping)\n",
    "        self.loss = build_loss(self.logits, self.targets, lstm_size, num_classes)\n",
    "        self.optimizer = build_optimizer(self.loss, learning_rate, grad_clip)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hyperparameters\n",
    "\n",
    "Here I'm defining the hyperparameters for the network. \n",
    "\n",
    "* `batch_size` - Number of sequences running through the network in one pass.\n",
    "* `num_steps` - Number of characters in the sequence the network is trained on. Larger is better typically, the network will learn more long range dependencies. But it takes longer to train. 100 is typically a good number here.\n",
    "* `lstm_size` - The number of units in the hidden layers.\n",
    "* `num_layers` - Number of hidden LSTM layers to use\n",
    "* `learning_rate` - Learning rate for training\n",
    "* `keep_prob` - The dropout keep probability when training. If you're network is overfitting, try decreasing this.\n",
    "\n",
    "Here's some good advice from Andrej Karpathy on training the network. I'm going to copy it in here for your benefit, but also link to [where it originally came from](https://github.com/karpathy/char-rnn#tips-and-tricks).\n",
    "\n",
    "> ## Tips and Tricks\n",
    "\n",
    ">### Monitoring Validation Loss vs. Training Loss\n",
    ">If you're somewhat new to Machine Learning or Neural Networks it can take a bit of expertise to get good models. The most important quantity to keep track of is the difference between your training loss (printed during training) and the validation loss (printed once in a while when the RNN is run on the validation data (by default every 1000 iterations)). In particular:\n",
    "\n",
    "> - If your training loss is much lower than validation loss then this means the network might be **overfitting**. Solutions to this are to decrease your network size, or to increase dropout. For example you could try dropout of 0.5 and so on.\n",
    "> - If your training/validation loss are about equal then your model is **underfitting**. Increase the size of your model (either number of layers or the raw number of neurons per layer)\n",
    "\n",
    "> ### Approximate number of parameters\n",
    "\n",
    "> The two most important parameters that control the model are `lstm_size` and `num_layers`. I would advise that you always use `num_layers` of either 2/3. The `lstm_size` can be adjusted based on how much data you have. The two important quantities to keep track of here are:\n",
    "\n",
    "> - The number of parameters in your model. This is printed when you start training.\n",
    "> - The size of your dataset. 1MB file is approximately 1 million characters.\n",
    "\n",
    ">These two should be about the same order of magnitude. It's a little tricky to tell. Here are some examples:\n",
    "\n",
    "> - I have a 100MB dataset and I'm using the default parameter settings (which currently print 150K parameters). My data size is significantly larger (100 mil >> 0.15 mil), so I expect to heavily underfit. I am thinking I can comfortably afford to make `lstm_size` larger.\n",
    "> - I have a 10MB dataset and running a 10 million parameter model. I'm slightly nervous and I'm carefully monitoring my validation loss. If it's larger than my training loss then I may want to try to increase dropout a bit and see if that helps the validation loss.\n",
    "\n",
    "> ### Best models strategy\n",
    "\n",
    ">The winning strategy to obtaining very good models (if you have the compute time) is to always err on making the network larger (as large as you're willing to wait for it to compute) and then try different dropout values (between 0,1). Whatever model has the best validation performance (the loss, written in the checkpoint filename, low is good) is the one you should use in the end.\n",
    "\n",
    ">It is very common in deep learning to run many different models with many different hyperparameter settings, and in the end take whatever checkpoint gave the best validation performance.\n",
    "\n",
    ">By the way, the size of your training and validation splits are also parameters. Make sure you have a decent amount of data in your validation set or otherwise the validation performance will be noisy and not very informative.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "batch_size = 100        # Sequences per batch\n",
    "num_steps = 100         # Number of sequence steps per batch\n",
    "lstm_size = 512         # Size of hidden layers in LSTMs\n",
    "num_layers = 2          # Number of LSTM layers\n",
    "learning_rate = 0.001   # Learning rate\n",
    "keep_prob = 0.5         # Dropout keep probability"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Time for training\n",
    "\n",
    "This is typical training code, passing inputs and targets into the network, then running the optimizer. Here we also get back the final LSTM state for the mini-batch. Then, we pass that state back into the network so the next batch can continue the state from the previous batch. And every so often (set by `save_every_n`) I save a checkpoint.\n",
    "\n",
    "Here I'm saving checkpoints with the format\n",
    "\n",
    "`i{iteration number}_l{# hidden layer units}.ckpt`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "epochs = 20\n",
    "# Save every N iterations\n",
    "save_every_n = 200\n",
    "\n",
    "model = CharRNN(len(vocab), batch_size=batch_size, num_steps=num_steps,\n",
    "                lstm_size=lstm_size, num_layers=num_layers, \n",
    "                learning_rate=learning_rate)\n",
    "\n",
    "saver = tf.train.Saver(max_to_keep=100)\n",
    "with tf.Session() as sess:\n",
    "    sess.run(tf.global_variables_initializer())\n",
    "    \n",
    "    # Use the line below to load a checkpoint and resume training\n",
    "    #saver.restore(sess, 'checkpoints/______.ckpt')\n",
    "    counter = 0\n",
    "    for e in range(epochs):\n",
    "        # Train network\n",
    "        new_state = sess.run(model.initial_state)\n",
    "        loss = 0\n",
    "        for x, y in get_batches(encoded, batch_size, num_steps):\n",
    "            counter += 1\n",
    "            start = time.time()\n",
    "            feed = {model.inputs: x,\n",
    "                    model.targets: y,\n",
    "                    model.keep_prob: keep_prob,\n",
    "                    model.initial_state: new_state}\n",
    "            batch_loss, new_state, _ = sess.run([model.loss, \n",
    "                                                 model.final_state, \n",
    "                                                 model.optimizer], \n",
    "                                                 feed_dict=feed)\n",
    "            \n",
    "            end = time.time()\n",
    "            print('Epoch: {}/{}... '.format(e+1, epochs),\n",
    "                  'Training Step: {}... '.format(counter),\n",
    "                  'Training loss: {:.4f}... '.format(batch_loss),\n",
    "                  '{:.4f} sec/batch'.format((end-start)))\n",
    "        \n",
    "            if (counter % save_every_n == 0):\n",
    "                saver.save(sess, \"checkpoints/i{}_l{}.ckpt\".format(counter, lstm_size))\n",
    "    \n",
    "    saver.save(sess, \"checkpoints/i{}_l{}.ckpt\".format(counter, lstm_size))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Saved checkpoints\n",
    "\n",
    "Read up on saving and loading checkpoints here: https://www.tensorflow.org/programmers_guide/variables"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "model_checkpoint_path: \"checkpoints/i3960_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i200_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i400_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i600_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i800_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i1000_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i1200_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i1400_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i1600_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i1800_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i2000_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i2200_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i2400_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i2600_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i2800_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i3000_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i3200_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i3400_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i3600_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i3800_l512.ckpt\"\n",
       "all_model_checkpoint_paths: \"checkpoints/i3960_l512.ckpt\""
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.train.get_checkpoint_state('checkpoints')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Sampling\n",
    "\n",
    "Now that the network is trained, we'll can use it to generate new text. The idea is that we pass in a character, then the network will predict the next character. We can use the new one, to predict the next one. And we keep doing this to generate all new text. I also included some functionality to prime the network with some text by passing in a string and building up a state from that.\n",
    "\n",
    "The network gives us predictions for each character. To reduce noise and make things a little less random, I'm going to only choose a new character from the top N most likely characters.\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def pick_top_n(preds, vocab_size, top_n=5):\n",
    "    p = np.squeeze(preds)\n",
    "    p[np.argsort(p)[:-top_n]] = 0\n",
    "    p = p / np.sum(p)\n",
    "    c = np.random.choice(vocab_size, 1, p=p)[0]\n",
    "    return c"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def sample(checkpoint, n_samples, lstm_size, vocab_size, prime=\"The \"):\n",
    "    samples = [c for c in prime]\n",
    "    model = CharRNN(len(vocab), lstm_size=lstm_size, sampling=True)\n",
    "    saver = tf.train.Saver()\n",
    "    with tf.Session() as sess:\n",
    "        saver.restore(sess, checkpoint)\n",
    "        new_state = sess.run(model.initial_state)\n",
    "        for c in prime:\n",
    "            x = np.zeros((1, 1))\n",
    "            x[0,0] = vocab_to_int[c]\n",
    "            feed = {model.inputs: x,\n",
    "                    model.keep_prob: 1.,\n",
    "                    model.initial_state: new_state}\n",
    "            preds, new_state = sess.run([model.prediction, model.final_state], \n",
    "                                         feed_dict=feed)\n",
    "\n",
    "        c = pick_top_n(preds, len(vocab))\n",
    "        samples.append(int_to_vocab[c])\n",
    "\n",
    "        for i in range(n_samples):\n",
    "            x[0,0] = c\n",
    "            feed = {model.inputs: x,\n",
    "                    model.keep_prob: 1.,\n",
    "                    model.initial_state: new_state}\n",
    "            preds, new_state = sess.run([model.prediction, model.final_state], \n",
    "                                         feed_dict=feed)\n",
    "\n",
    "            c = pick_top_n(preds, len(vocab))\n",
    "            samples.append(int_to_vocab[c])\n",
    "        \n",
    "    return ''.join(samples)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here, pass in the path to a checkpoint and sample from the network."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'checkpoints/i3960_l512.ckpt'"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tf.train.latest_checkpoint('checkpoints')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Farens. The man and this presence there was a long whell against the\n",
      "serfice. And she would have seen a dest of sorry of any arrangements,\n",
      "true. But the side house was in the money about the chief same, and\n",
      "that he had to say to all his face, with some significance for his wife\n",
      "that he had come and done to the penslition of a little and sounds of\n",
      "the society, and always was in her and horses, and that that they\n",
      "could not be a chalacteristic on her so second become in her soul that it\n",
      "was all sincerity, and her clothest having been talking to horror of\n",
      "a presine that was something was all his mother had no station.\n",
      "\n",
      "The conversation had suddenly had not been anything.\n",
      "\n",
      "Sometying her health she came over the services, had sat down again, and\n",
      "at the conversation was not in the strange shreed to the door, and with\n",
      "his sight of the party, the sick man thinking the secret of the mother\n",
      "when they had brought a humored friends, and the prayers that he could not\n",
      "let him any more and marking this subject, and they. The most of the\n",
      "steps about indeed and triude of accordance of the position that was\n",
      "steping himself. They went to those on its ordered and could only to\n",
      "chart how when they all and the more angry and the sight about it. The\n",
      "peasants would his chillish accounts and at the partious feeling of\n",
      "particularly ares, and so thore was some shorting or them. And he so\n",
      "see the place, who was seeing that something coughed hands, as though\n",
      "he was at the same, was not that she was insulted. Anna had been at\n",
      "the prince tell out his hat this article and to begin to say.\n",
      "\n",
      "\"Yive were a selvated the members who have taken the station in which the\n",
      "committee? And is to darling a minutate of the carriage of marnated. I have\n",
      "took to myself in the steps and a single morn and happy.\n",
      "\n",
      "\"If that's into a single morning, this seems thanks to her and thinks\n",
      "in the past and married that.\" The side had been carrying all the\n",
      "portrait of his words. These wires he was sitting at the past of the\n",
      "room, t\n"
     ]
    }
   ],
   "source": [
    "checkpoint = tf.train.latest_checkpoint('checkpoints')\n",
    "samp = sample(checkpoint, 2000, lstm_size, len(vocab), prime=\"Far\")\n",
    "print(samp)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Fard,. \"\"\n",
      "I\"\n",
      "\"The tat he winse he wos al alt ong the hor an thot hed the\n",
      "the sin the sand we he sos and se th the an soris he as oth thes sosed to he the he ad ond on har soron sha sas sitis he\n",
      "sentes the he wis as arton thir ans on ond wome to whe the sos thes sosit to the sos at hos ho she sis the she him an tot thes th an setit and wo he angeras,. Tho hor ho wot thime\n",
      "ne tot ha to we we he won thar al thir send tougtint ting and wh wamed, the the her what hot wit he wat ta hor,\n",
      "thing th whe tousederede sad sithe the the he se the han shes oress ho tot on here an on ot to he the hhressasesid thise where ans andentor tat the\n",
      "t the won he ans te hite wost hin sos torinnd\n",
      "tare wes to se her\n",
      "inntor ond tot ont ate\n",
      "silt wens\n",
      "thithe serede ho he the soros, sers on asis and af on he tare sin hos tot this won oth the he tins, and ang tee ar the ant oud thot woth shat ha lan and sithe to she sos thoth sar te sesilt ther singetin to he te artor to ho he\n",
      "wans he ans her han ase and. \n",
      "I hind wasd we\n"
     ]
    }
   ],
   "source": [
    "checkpoint = 'checkpoints/i200_l512.ckpt'\n",
    "samp = sample(checkpoint, 1000, lstm_size, len(vocab), prime=\"Far\")\n",
    "print(samp)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Farnt out a seenen a doons the werly a prostines, bet of to as sone him, al her his and and she had booke word and wert on her had and said to hoult, sto stat to she whon he wond to\n",
      "denting andond to stat had he he seed a sele to the corsions.\n",
      "\n",
      "\"The more when she shead, thourg that so he had and to the sort, sunding him his and and sace and where her\n",
      "headed he said\n",
      "the haress and than sheard her, her has thing his stalith his tongenter,\" she said\n",
      "he dasted the monting the sond of\n",
      "their that allong her ald\n",
      "have a tare him that to the somesily wourd to dive in and som her and whene the sead, and withon te said whine and had sail he having had\n",
      "same to sime her has\n",
      "antanter that\n",
      "she she distlidion of her heally.\n",
      "\n",
      "\"When, an the pinter on atone with\n",
      "the pasting, and sooked hus a sant of her his her ander was having to the panting to shingher at a shiss to thow to and walk nover as to the portanion,\" said Stepan'Akknavitcch with which all she coundsess in this his,\n",
      "boring the panitiou, and\n",
      "atonge\n"
     ]
    }
   ],
   "source": [
    "checkpoint = 'checkpoints/i600_l512.ckpt'\n",
    "samp = sample(checkpoint, 1000, lstm_size, len(vocab), prime=\"Far\")\n",
    "print(samp)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Farcial the\n",
      "confiring to the mone of the correm and thinds. She\n",
      "she saw the\n",
      "streads of herself hand only astended of the carres to her his some of the princess of which he came him of\n",
      "all that his white the dreasing of\n",
      "thisking the princess and with she was she had\n",
      "bettee a still and he was happined, with the pood on the mush to the peaters and seet it.\n",
      "\n",
      "\"The possess a streatich, the may were notine at his mate a misted\n",
      "and the\n",
      "man of the mother at the same of the seem her\n",
      "felt. He had not here.\n",
      "\n",
      "\"I conest only be alw you thinking that the partion\n",
      "of their said.\"\n",
      "\n",
      "\"A much then you make all her\n",
      "somether. Hower their centing\n",
      "about\n",
      "this, and I won't give it in\n",
      "himself.\n",
      "I had not come at any see it will that there she chile no one that him.\n",
      "\n",
      "\"The distiction with you all.... It was\n",
      "a mone of the mind were starding to the simple to a mone. It to be to ser in the place,\" said Vronsky.\n",
      "\"And a plais in\n",
      "his face, has alled in the consess on at they to gan in the sint\n",
      "at as that\n",
      "he would not be and t\n"
     ]
    }
   ],
   "source": [
    "checkpoint = 'checkpoints/i1200_l512.ckpt'\n",
    "samp = sample(checkpoint, 1000, lstm_size, len(vocab), prime=\"Far\")\n",
    "print(samp)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
